package com.jzo2o.market.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.JsonUtils;
import com.jzo2o.common.utils.StringUtils;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.mapper.CouponWriteOffMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CountResDTO;
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.jzo2o.market.constants.RedisConstants.RedisKey.ACTIVITY_CACHE_LIST;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.COUPON_RESOURCE_STOCK;
import static com.jzo2o.market.enums.ActivityStatusEnum.DISTRIBUTING;
import static com.jzo2o.market.enums.ActivityStatusEnum.NO_DISTRIBUTE;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponService couponService;
    @Resource
    private CouponMapper couponMapper;

    @Resource
    private ICouponWriteOffService couponWriteOffService;
    @Resource
    private CouponWriteOffMapper couponWriteOffMapper;

    @Override
    public ActivityInfoResDTO getActivityInfoByIdFromCache(Long id) {
        //1. 缓存中获取活动信息
        String jsonString = (String) redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (StringUtils.isEmpty(jsonString)) {
            return null;
        }

        //2. 字符串转换为集合
        List<ActivityInfoResDTO> activityInfoResDTOList = JSON.parseArray(jsonString, ActivityInfoResDTO.class);
        if (CollUtil.isEmpty(activityInfoResDTOList)) {
            return null;
        }

        //3. 过滤出指定id的活动
        return activityInfoResDTOList.stream()
                .filter(e -> e.getId().equals(id))
                .findFirst().orElse(null);
    }
    @Override
    public List<SeizeCouponInfoResDTO> queryForListFromCache(Integer tabType) {
        //1. 从Redis中查询优惠券活动的数据
        String jsonStr = (String) redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (StringUtils.isEmpty(jsonStr)) {
            return List.of();
        }

        //2. 将查询到的JSON字符串转换为集合List<SeizeCouponInfoResDTO>
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS
                = JSON.parseArray(jsonStr, SeizeCouponInfoResDTO.class);
        if (CollUtil.isEmpty(seizeCouponInfoResDTOS)) {
            return List.of();
        }

        //3. 根据tabType的值筛选符合条件的活动返回
        return seizeCouponInfoResDTOS.stream().filter(e -> {
            //获取当前获得真实的状态
            int status = getStatus(e.getDistributeStartTime(), e.getDistributeEndTime(), e.getStatus());
//            int status = e.getStatus();
            if (tabType == 1) {//筛选疯抢中的
                return status == DISTRIBUTING.getStatus();
            } else {//筛选即将开始的
                return status == NO_DISTRIBUTE.getStatus();
            }
        }).map(e->{
            e.setRemainNum(e.getStockNum());
            return e;
        }).collect(Collectors.toList());
    }



    /**
     * 根据活动的目前状态、开始、结束时间 对比当前时间来获取到活动的真实状态
     * 1. 状态在待生效, 但是 活动开始时间 <=当前时间 < 活动结束时间  真实状态应该是 进行中
     * 2. 状态在待生效, 但是 活动结束时间 < 当前时间               真实状态应该是 已结束
     * 3. 状态在进行中, 但是 活动结束时间 < 当前时间               真实状态应该是 已结束
     * 4. 其它情况, 当前状态就是真实状态
     *
     * @param distributeStartTime 活动开始时间
     * @param distributeEndTime   活动结束时间
     * @param status              当前状态
     * @return 活动的真实状态
     */
    private int getStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        if (NO_DISTRIBUTE.getStatus() == status && distributeStartTime.isBefore(DateUtils.now()) && distributeEndTime.isAfter(LocalDateTime.now())) {
            //状态在待生效, 但是 活动开始时间<=当前时间<活动结束时间  真实状态应该是 进行中`
            return DISTRIBUTING.getStatus();
        } else if (NO_DISTRIBUTE.getStatus() == status && distributeEndTime.isBefore(LocalDateTime.now())) {
            //状态在待生效, 但是 活动结束时间 < 当前时间   真实状态应该是 已结束
            return ActivityStatusEnum.LOSE_EFFICACY.getStatus();
        } else if (DISTRIBUTING.getStatus() == status && distributeEndTime.isBefore(LocalDateTime.now())) {
            //状态在进行中, 但是 活动结束时间 < 当前时间   真实状态应该是 已结束
            return ActivityStatusEnum.LOSE_EFFICACY.getStatus();
        }
        return status;
    }

    @Override
    public void preHeat() {
        //1. 查询状态是待开始或者进行中，并且发放开始时间距离现在不足1个月的活动，按照开始时间升序排列
        //select * from activity where status in (1,2)  and distribute_start_time < 当前时间+1个月 order by distribute_start_time asc
        List<Activity> list = this.lambdaQuery()
                //status in (1,2)
                .in(Activity::getStatus, NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus())
                .lt(Activity::getDistributeStartTime, LocalDateTime.now().plusMonths(1))//distribute_start_time < 当前时间+1个月
                .orderByAsc(Activity::getDistributeStartTime)//order by distribute_start_time asc
                .list();
        if (CollUtil.isEmpty(list)){
            list = new ArrayList<>();//防止缓存穿透
        }


        //2. 将查询到的数据封装到List<SeizeCouponInfoResDTO>，再转换为JSON串
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = BeanUtils.copyToList(list, SeizeCouponInfoResDTO.class);
        String jsonStr = JsonUtils.toJsonStr(seizeCouponInfoResDTOS);

        //3. 将JSON字符串存入redis
        redisTemplate.opsForValue().set(ACTIVITY_CACHE_LIST,jsonStr);
        //4. 将优惠券活动的库存从MySQL同步到Redis
        //4-1 对于待生效的活动更新库存
        list.stream().filter(e ->
                getStatus(e.getDistributeStartTime(), e.getDistributeEndTime(), e.getStatus()) == NO_DISTRIBUTE.getStatus()
        ).forEach(e ->
                redisTemplate.opsForHash().put(String.format(COUPON_RESOURCE_STOCK, e.getId() % 10), e.getId(), e.getStockNum())
        );

        //4-2 对于已生效的活动如果库存已经同步则不再同步
        list.stream().filter(e ->
                getStatus(e.getDistributeStartTime(), e.getDistributeEndTime(), e.getStatus()) == DISTRIBUTING.getStatus()
        ).forEach(e ->
                //只有库存不存在的情况下, 才要进行保存操作
                redisTemplate.opsForHash().putIfAbsent(String.format(COUPON_RESOURCE_STOCK, e.getId() % 10), e.getId(), e.getStockNum())
        );
    }

    /**
     * 修改活动状态
     */
    @Override
    public void updateStatus() {
        //活动状态包括：1 待生效，2 进行中，3 已失效，4 作废
        //对于待生效的活动到达发放开始时间状态改为进行中
        //update activity set status = 2 where status =1 and distribute_start_time <= 当前时间 and distribute_end_time >= 当前时间
        this.lambdaUpdate()
                .eq(Activity::getStatus, NO_DISTRIBUTE.getStatus())
                .le(Activity::getDistributeStartTime, LocalDateTime.now())
                .ge(Activity::getDistributeEndTime, LocalDateTime.now())
                .set(Activity::getStatus, DISTRIBUTING.getStatus())
                .update();
        //对于待生效及进行中的活动到达发放结束时间状态改为已失效
        //update activity set status = 4 where status in (1,2) and distribute_end_time < 当前时间
        this.lambdaUpdate()
                .in(Activity::getStatus, NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus())
                .le(Activity::getDistributeEndTime, LocalDateTime.now())
                .set(Activity::getStatus, ActivityStatusEnum.VOIDED.getStatus())
                .update();
    }

    /**
     * 撤销优惠券活动
     *
     * @param id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long id) {
        //校验活动是否存在
        Activity activity = this.getById(id);
        if (ObjectUtil.isEmpty(activity)){
            throw new ForbiddenOperationException("活动不存在");
        }
        //校验活动状态
        if(activity.getStatus() != NO_DISTRIBUTE.getStatus() &&
                activity.getStatus() != DISTRIBUTING.getStatus()){
            throw new ForbiddenOperationException("活动状态不支持撤销");
        }
        //对于待生效及进行中的活动如果要进行终止，可以执行撤销操作
        //status 活动状态1：待生效，2：进行中，3：已失效4：作废
        //update activity set status = 4 where id = 活动id and status in (1,2)
        this.lambdaUpdate()
                .eq(Activity::getId, id)
                .in(Activity::getStatus, NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus())
                .set(Activity::getStatus,ActivityStatusEnum.VOIDED.getStatus())
                .update();
        //执行撤销操作后此活动将终止，用户已抢到还未使用的优惠券将作废
        //update coupon set status = 4 where activity_id = 活动id and status = 1
        couponService.lambdaUpdate()
                .eq(Coupon::getActivityId, id)
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .set(Coupon::getStatus, CouponStatusEnum.VOIDED.getStatus())
                .update();
    }

    /**
     * 根据id查询优惠券活动
     *
     * @param id
     * @return
     */
    @Override
    public ActivityInfoResDTO findById(Long id) {
        //1、查询数据库
        Activity activity = this.getById(id);
        if (ObjectUtil.isEmpty(activity)){
            throw new ForbiddenOperationException("活动不存在");
        }
        ActivityInfoResDTO activityInfoResDTO = BeanUtil.copyProperties(activity, ActivityInfoResDTO.class);
        //2、补充字段
        //优惠券领取数量
        Integer receiveNum = couponService.lambdaQuery().eq(Coupon::getActivityId, id).count();
        activityInfoResDTO.setReceiveNum(receiveNum);
        //优惠券核销数量
        Integer writeOffNum = couponWriteOffService.lambdaQuery().eq(CouponWriteOff::getActivityId, id).count();
        activityInfoResDTO.setWriteOffNum(writeOffNum);
        //3、返回结果
        return activityInfoResDTO;
    }

    /**
     * 分页查询优惠券活动
     *
     * @param dto
     * @return
     */
    @Override
    public PageResult<ActivityInfoResDTO> findByPage(ActivityQueryForPageReqDTO dto) {
        //设置分页参数
        Page<Activity> page = PageUtils.parsePageQuery(dto, Activity.class);
        //设置查询条件
        LambdaQueryWrapper<Activity> wrapper = new LambdaQueryWrapper<Activity>()
                .eq(dto.getId() != null,Activity::getId, dto.getId())
                .like(ObjectUtil.isNotEmpty(dto.getName()),Activity::getName, dto.getName())
                .eq(dto.getStatus()  != null,Activity::getStatus, dto.getStatus())
                .eq(dto.getType() != null,Activity::getType, dto.getType());
        //查询
        page=this.page(page, wrapper);
        if (ObjectUtil.isEmpty(page.getRecords())){
            return new PageResult<ActivityInfoResDTO>( page.getPages(),page.getTotal(),  List.of());
        }

        //封装结果
        //优化此处  循环中不能做数据库查询 feign调用
        //in关键词 一次查询多个活动id 结果封装到map中（id做key count 做value） 在循环中根据key 获取value
        //封装所有的活动id 到一个集合中
        List<Long> activityIdList = page.getRecords().stream().map(Activity::getId).collect(Collectors.toList());

        //根据活动id 查询优惠券领取数量
        List<CountResDTO> countResDTOList1 = couponMapper.countByActivityIdList(activityIdList);
        Map<Long, Integer> map1 = countResDTOList1.stream().collect(Collectors.toMap(CountResDTO::getActivityId, CountResDTO::getNum));

        //根据活动id 获取优惠券核销数量
        List<CountResDTO> countResDTOList2 = couponWriteOffMapper.countByActivityIdList(activityIdList);
        Map<Long, Integer> map2 = countResDTOList2.stream().collect(Collectors.toMap(CountResDTO::getActivityId, CountResDTO::getNum));


        List<ActivityInfoResDTO> list = page.getRecords().stream().map(item -> {
            ActivityInfoResDTO activityInfoResDTO = BeanUtil.copyProperties(item, ActivityInfoResDTO.class);
            //优惠券领取数量
            //根据活动id 到优惠券表 查询优惠券领取数量
//            Integer receiveNum = couponService.lambdaQuery().eq(Coupon::getActivityId,item.getId()).count();
            activityInfoResDTO.setReceiveNum(map1.get(item.getId()));
            //优惠券核销数量
//            Integer writeOffNum = couponWriteOffService.lambdaQuery().eq(CouponWriteOff::getActivityId, item.getId()).count();
            activityInfoResDTO.setWriteOffNum(map2.get(item.getId()));
            return activityInfoResDTO;
        }).collect(Collectors.toList());

        //返回
        return new PageResult<ActivityInfoResDTO>(page.getPages(),page.getTotal(),list);
    }

    /**
     * 保存或者更新优惠券活动
     *
     * @param dto
     */
    @Override
    public void saveOrUpdateActivity(ActivitySaveReqDTO dto) {
        //参数校验
        dto.check();

        //copy dto中有的字段到实体中
        Activity activity = BeanUtil.copyProperties(dto, Activity.class);

        //补全字段(状态、库存)
        //待生效
        activity.setStatus(NO_DISTRIBUTE.getStatus());
        //库存==优惠券发放数量
        activity.setStockNum(activity.getTotalNum());

        //保存或者更新数据库
        this.saveOrUpdate(activity);
    }
}
